Svenska

Lär dig skalbara designmönster för GraphQL-scheman för att bygga robusta och underhållbara API:er som möter behoven hos en global publik. Bemästra schema stitching, federation och modularisering.

Design av GraphQL-schema: Skalbara mönster för globala API:er

GraphQL har vuxit fram som ett kraftfullt alternativ till traditionella REST API:er, och erbjuder klienter flexibiliteten att begära exakt den data de behöver. Men i takt med att ditt GraphQL API växer i komplexitet och omfattning – särskilt när det betjänar en global publik med varierande datakrav – blir en noggrann schemadesign avgörande för underhåll, skalbarhet och prestanda. Denna artikel utforskar flera skalbara designmönster för GraphQL-scheman för att hjälpa dig bygga robusta API:er som kan hantera kraven från en global applikation.

Vikten av en skalbar schemadesign

Ett väl utformat GraphQL-schema är grunden för ett framgångsrikt API. Det dikterar hur klienter kan interagera med dina data och tjänster. Dålig schemadesign kan leda till ett antal problem, inklusive:

För globala applikationer förstärks dessa problem. Olika regioner kan ha olika datakrav, regulatoriska begränsningar och prestandaförväntningar. En skalbar schemadesign gör att du kan hantera dessa utmaningar på ett effektivt sätt.

Grundläggande principer för skalbar schemadesign

Innan vi dyker in i specifika mönster, låt oss beskriva några grundläggande principer som bör vägleda din schemadesign:

Skalbara designmönster för scheman

Här är flera skalbara designmönster som du kan använda för att bygga robusta GraphQL API:er:

1. Schema Stitching

Schema stitching låter dig kombinera flera GraphQL API:er till ett enda, enhetligt schema. Detta är särskilt användbart när du har olika team eller tjänster som ansvarar för olika delar av din data. Det är som att ha flera mini-API:er och foga samman dem via ett 'gateway'-API.

Hur det fungerar:

  1. Varje team eller tjänst exponerar sitt eget GraphQL API med sitt eget schema.
  2. En central gateway-tjänst använder verktyg för schema stitching (som Apollo Federation eller GraphQL Mesh) för att slå samman dessa scheman till ett enda, enhetligt schema.
  3. Klienter interagerar med gateway-tjänsten, som dirigerar förfrågningar till de lämpliga underliggande API:erna.

Exempel:

Föreställ dig en e-handelsplattform med separata API:er för produkter, användare och ordrar. Varje API har sitt eget schema:

  
    # Produkt-API
    type Product {
      id: ID!
      name: String!
      price: Float!
    }

    type Query {
      product(id: ID!): Product
    }

    # Användar-API
    type User {
      id: ID!
      name: String!
      email: String!
    }

    type Query {
      user(id: ID!): User
    }

    # Order-API
    type Order {
      id: ID!
      userId: ID!
      productId: ID!
      quantity: Int!
    }

    type Query {
      order(id: ID!): Order
    }
  

Gateway-tjänsten kan sy ihop (stitch) dessa scheman för att skapa ett enhetligt schema:

  
    type Product {
      id: ID!
      name: String!
      price: Float!
    }

    type User {
      id: ID!
      name: String!
      email: String!
    }

    type Order {
      id: ID!
      user: User! @relation(field: "userId")
      product: Product! @relation(field: "productId")
      quantity: Int!
    }

    type Query {
      product(id: ID!): Product
      user(id: ID!): User
      order(id: ID!): Order
    }
  

Notera hur Order-typen nu inkluderar referenser till User och Product, även om dessa typer definieras i separata API:er. Detta uppnås genom direktiv för schema stitching (som @relation i detta exempel).

Fördelar:

Att tänka på:

2. Schemafederation

Schemafederation är en vidareutveckling av schema stitching, utformad för att hantera några av dess begränsningar. Den erbjuder ett mer deklarativt och standardiserat tillvägagångssätt för att komponera GraphQL-scheman.

Hur det fungerar:

  1. Varje tjänst exponerar ett GraphQL API och annoterar sitt schema med federationsdirektiv (t.ex. @key, @extends, @external).
  2. En central gateway-tjänst (med Apollo Federation) använder dessa direktiv för att bygga en supergraf – en representation av hela det federerade schemat.
  3. Gateway-tjänsten använder supergrafen för att dirigera förfrågningar till lämpliga underliggande tjänster och lösa beroenden.

Exempel:

Med samma e-handelsexempel kan de federerade schemana se ut så här:

  
    # Produkt-API
    type Product @key(fields: "id") {
      id: ID!
      name: String!
      price: Float!
    }

    type Query {
      product(id: ID!): Product
    }

    # Användar-API
    type User @key(fields: "id") {
      id: ID!
      name: String!
      email: String!
    }

    type Query {
      user(id: ID!): User
    }

    # Order-API
    type Order {
      id: ID!
      userId: ID!
      productId: ID!
      quantity: Int!
      user: User! @requires(fields: "userId")
      product: Product! @requires(fields: "productId")
    }

    extend type Query {
      order(id: ID!): Order
    }
  

Notera användningen av federationsdirektiv:

Fördelar:

Att tänka på:

3. Modulär schemadesign

Modulär schemadesign innebär att man bryter ner ett stort, monolitiskt schema i mindre, mer hanterbara moduler. Detta gör det lättare att förstå, ändra och återanvända enskilda delar av ditt API, även utan att använda federerade scheman.

Hur det fungerar:

  1. Identifiera logiska gränser inom ditt schema (t.ex. användare, produkter, ordrar).
  2. Skapa separata moduler för varje gräns, som definierar typer, frågor och mutationer relaterade till den gränsen.
  3. Använd import/export-mekanismer (beroende på din GraphQL-serverimplementation) för att kombinera modulerna till ett enda, enhetligt schema.

Exempel (med JavaScript/Node.js):

Skapa separata filer för varje modul:

  
    // users.graphql
    type User {
      id: ID!
      name: String!
      email: String!
    }

    type Query {
      user(id: ID!): User
    }

    // products.graphql
    type Product {
      id: ID!
      name: String!
      price: Float!
    }

    type Query {
      product(id: ID!): Product
    }
  

Kombinera dem sedan i din huvudschemafil:

  
    // schema.js
    const { makeExecutableSchema } = require('graphql-tools');
    const { typeDefs: userTypeDefs, resolvers: userResolvers } = require('./users');
    const { typeDefs: productTypeDefs, resolvers: productResolvers } = require('./products');

    const typeDefs = [
      userTypeDefs,
      productTypeDefs,
      ""
    ];

    const resolvers = {
      Query: {
        ...userResolvers.Query,
        ...productResolvers.Query,
      }
    };

    const schema = makeExecutableSchema({
      typeDefs,
      resolvers,
    });

    module.exports = schema;
  

Fördelar:

Att tänka på:

4. Gränssnitt (Interface) och union-typer

Gränssnitt (interface) och union-typer låter dig definiera abstrakta typer som kan implementeras av flera konkreta typer. Detta är användbart för att representera polymorf data – data som kan anta olika former beroende på sammanhanget.

Hur det fungerar:

Exempel:

  
    interface Node {
      id: ID!
    }

    type User implements Node {
      id: ID!
      name: String!
      email: String!
    }

    type Product implements Node {
      id: ID!
      name: String!
      price: Float!
    }

    union SearchResult = User | Product

    type Query {
      node(id: ID!): Node
      search(query: String!): [SearchResult!]!
    }
  

I detta exempel implementerar både User och Product gränssnittet Node, som definierar ett gemensamt id-fält. Union-typen SearchResult representerar ett sökresultat som kan vara antingen en User eller en Product. Klienter kan skicka en fråga till search-fältet och sedan använda __typename-fältet för att avgöra vilken typ av resultat de fick.

Fördelar:

Att tänka på:

5. Connection-mönstret

Connection-mönstret är ett standardiserat sätt att implementera paginering i GraphQL API:er. Det erbjuder ett konsekvent och effektivt sätt att hämta stora datalistor i bitar.

Hur det fungerar:

Exempel:

  
    type User {
      id: ID!
      name: String!
      email: String!
    }

    type UserEdge {
      node: User!
      cursor: String!
    }

    type UserConnection {
      edges: [UserEdge!]!
      pageInfo: PageInfo!
    }

    type PageInfo {
      hasNextPage: Boolean!
      hasPreviousPage: Boolean!
      startCursor: String
      endCursor: String
    }

    type Query {
      users(first: Int, after: String, last: Int, before: String): UserConnection!
    }
  

Fördelar:

Att tänka på:

Globala överväganden

När du designar ett GraphQL-schema för en global publik, tänk på dessa ytterligare faktorer:

Tänk till exempel på ett fält för produktbeskrivning:


type Product {
 id: ID!
 name: String!
 description(language: String = "en"): String!
}

Detta låter klienter begära beskrivningen på ett specifikt språk. Om inget språk anges, används engelska (en) som standard.

Slutsats

En skalbar schemadesign är avgörande för att bygga robusta och underhållbara GraphQL API:er som kan hantera kraven från en global applikation. Genom att följa principerna som beskrivs i denna artikel och använda lämpliga designmönster kan du skapa API:er som är lätta att förstå, ändra och utöka, samtidigt som de erbjuder utmärkt prestanda och skalbarhet. Kom ihåg att modularisera, komponera och abstrahera ditt schema, och att ta hänsyn till de specifika behoven hos din globala publik.

Genom att anamma dessa mönster kan du låsa upp den fulla potentialen hos GraphQL och bygga API:er som kan driva dina applikationer i många år framöver.